﻿Imports System.Text
Imports System.Net.Sockets

Public Class TCPClient

#Region " Declarations "
    Private myClient As System.Net.Sockets.TcpClient
    Private Const BYTES_TO_READ As Integer = 1024
    Private readBuffer(BYTES_TO_READ) As Byte
    Private myStartupFormRef As Startup
    Private myPosition As String
    Private Const MESSAGE_DELIMITER As Char = ControlChars.Cr
    Private myLeftOvers As String
    Private myServerIp As String
    Private WithEvents myPingTimer As Timer
    Private _WaitHandle_ReadComplete As New System.Threading.AutoResetEvent(False)
#End Region

#Region " Properties "

    Public ReadOnly Property Client As System.Net.Sockets.TcpClient
        Get
            Return myClient
        End Get
    End Property

#End Region

#Region " Constructor "

    Public Sub New(ByVal ip As String)
        myServerIp = ip
        myPingTimer = New Timer()
        'ping timer of 30 seconds
        myPingTimer.Interval = 30000
    End Sub

#End Region
    '//todo

#Region " Destruction "
    ''' <summary>
    ''' Cleans up the tcp client when exit condition occurs(close form, connection lost)
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Close()
        _WaitHandle_ReadComplete.Dispose()
        Try
            myClient.Client.Shutdown(SocketShutdown.Both)
        Catch ex As Exception
            'object is probably already disposed
        End Try
        Try
            myClient.Close()
        Catch ex As Exception
            'object is probably already disposed
        End Try
        Try
            myPingTimer.Dispose()
        Catch ex As Exception

        End Try
    End Sub

#End Region

#Region " Initialization "

    Public Sub StartClient(ByRef parentForm As Startup)
        myClient = New System.Net.Sockets.TcpClient(myServerIp, 43001)
        myPingTimer.Start()
        myStartupFormRef = parentForm
        myClient.GetStream.BeginRead(readBuffer, 0, BYTES_TO_READ, AddressOf myReadCallBack, Nothing)
        SendMessage("CONNECT|" & Me.myStartupFormRef.txtUsername.Text)
    End Sub

#End Region

#Region " Read/Send Functions "

    ''' <summary>
    ''' Somehow this reads asynchronously and works, no idea why
    ''' </summary>
    ''' <param name="ar"></param>
    ''' <remarks></remarks>
    Private Sub myReadCallBack(ByVal ar As IAsyncResult)
        Dim myCompleteMessage As [String] = ""
        Dim numberOfBytesRead As Integer
        Try
            numberOfBytesRead = myClient.GetStream.EndRead(ar)
        Catch ex As Exception
            'The underlying socket have probably been closed OR an error has occured whilst trying to access it, either way, this is where you should remove close all eventuall connections
            'to this client and remove it from the list of connected clients.
            Me.Close()
        End Try
        myCompleteMessage = [String].Concat(myCompleteMessage, Encoding.ASCII.GetString(readBuffer, 0, numberOfBytesRead))
        ' message received may be larger than buffer size so loop through until you have it all.
        While myClient.GetStream.DataAvailable
            Try
                myClient.GetStream.BeginRead(readBuffer, 0, readBuffer.Length, New AsyncCallback(AddressOf Me.myReadCallBack), myClient.GetStream)
            Catch ex As Exception
                'The underlying socket have probably been closed OR an error has occured whilst trying to access it, either way, this is where you should remove close all eventuall connections
                'to this client and remove it from the list of connected clients.
                Me.Close()
            End Try
        End While
        ' Print out the received message to the console.
        messageReceived(myCompleteMessage)
        If Not myClient.Connected Then
            Me.Close()
        Else
            Try
                myClient.GetStream.BeginRead(readBuffer, 0, readBuffer.Length, New AsyncCallback(AddressOf Me.myReadCallBack), myClient.GetStream)
            Catch ex As Exception
                'The underlying socket have probably been closed OR an error has occured whilst trying to access it, either way, this is where you should remove close all eventuall connections
                'to this client and remove it from the list of connected clients.
                Me.Close()
            End Try
        End If
    End Sub 'myReadCallBack

    ''' <summary>
    ''' Locks the connection, sends a message to the server, waits for the read event to complete, then releases the connection
    ''' </summary>
    ''' <param name="msg">Message to be sent to the server</param>
    ''' <remarks></remarks>
    Public Sub SendMessage(ByVal msg As String)
        Dim sw As IO.StreamWriter
        Try
            SyncLock myClient.GetStream
                sw = New IO.StreamWriter(myClient.GetStream)
                sw.Write(msg & vbCr)
                sw.Flush()
                'wait fo response
                _WaitHandle_ReadComplete.WaitOne(10000)
            End SyncLock
        Catch ex As Exception
            'MessageBox.Show(ex.ToString)
            Me.Close()
        End Try
    End Sub

    ''' <summary>
    ''' Locks the connection, sends a message to the server, then releases the connection
    ''' </summary>
    ''' <param name="msg">Message to be sent to the server</param>
    ''' <remarks></remarks>
    Public Sub SendMessageAsync(ByVal msg As String)
        Dim sw As IO.StreamWriter
        Try
            SyncLock myClient.GetStream
                sw = New IO.StreamWriter(myClient.GetStream)
                sw.Write(msg & vbCr)
                sw.Flush()
                'don't wait for response
            End SyncLock
        Catch ex As Exception
            ' MessageBox.Show(ex.ToString)
            Me.Close()
        End Try
    End Sub

#End Region

#Region " Deserialization "

    'Analyzes string from server and reacts
    Private Sub messageReceived(ByVal message As String)
        Dim myCommands() As String = message.Split(vbCr)
        For Each line As String In myCommands
            Try
                'A message has been received from the server.
                Dim data() As String = line.Split("|"c) 'Split the message on each | and place in the string array.
                Select Case data(0)
                    Case "RESPONSE"
                        Select Case data(1)
                            Case "UPDATE"
                                'Update request of client GUI
                                Select Case data(2)
                                    Case "lblConnection"
                                        myStartupFormRef.SetConnectionLabelText(data(3))
                                    Case "Position"
                                        myPosition = data(3)
                                End Select
                            Case "PROMPT"
                                Select Case data(2)
                                    Case "txtUsername"
                                        myStartupFormRef.PromptUserForNewUsername()
                                        Me.Close()
                                End Select
                        End Select
                    Case "PUSH"
                        Select Case data(1)
                            Case "UPDATE"
                                Select Case data(2)
                                    Case "lstTablePlayers"
                                        myStartupFormRef.AddPlayerToListView(data(3))
                                    Case "lblPlayersJoined"
                                        If IsNumeric(data(3)) Then
                                            myStartupFormRef.SetPlayerCountLabelText(data(3))
                                        End If
                                End Select
                        End Select
                End Select
            Catch
                _WaitHandle_ReadComplete.Set()
            End Try
        Next
        _WaitHandle_ReadComplete.Set()
    End Sub
#End Region

#Region " Delegate Functions "

    Public Delegate Function PositionDelegate() As Position

    ''' <summary>
    ''' Request the clients position from the server
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetPosition() As Position
        Me.SendMessage("GET|Position")
        If myPosition Is Nothing Then
            _WaitHandle_ReadComplete.WaitOne()
        End If
        If myPosition IsNot Nothing Then
            Select Case myPosition
                Case "0"
                    Return Position.First
                Case "1"
                    Return Position.Second
                Case "2"
                    Return Position.Third
                Case "3"
                    Return Position.Fourth
            End Select
            'add a timeout
        End If
        Return Nothing
    End Function

#End Region

#Region " Events "

    ''' <summary>
    ''' Sends a ping to the server to keep connection alive. (set at 30 seconds time of writing)
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub PingTimer_TickEvent(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles myPingTimer.Tick
        'Sends a ping to the server to keep connection alive
        Me.SendMessageAsync("PING")
    End Sub

#End Region

End Class
